package main {
  import flash.display.*;
  import flash.events.*;
  import fl.events.*;
  import flash.text.*;
  import flash.ui.*;
  import flash.net.*;
  import flash.geom.Point;
  import main.events.*;
  import caurina.transitions.Tweener;
  import main.Main;
  
  /**
   * MapContainer class
   *
   * Shows world map and interacts with user providing abilities to scale, resize and handle mouse click events on it.
   *
   */
  public class MapContainer extends MovieClip {
    private const MAX_SCALE:Number = 6;
    private const MIN_SCALE:Number = 1;
    
    public static var CLICK_ON_PLACE:String = "clickOnPlace";
    
    
    public static var MODE_DRAG_MAP  = 0;
    public static var MODE_PUT_PLACE = 1;
    public static var MODE_MODAL_WIN = 2;
    public static var MODE_SCALE     = 3;
    
    private var _scale:Number = 1;
    private var _read_only:Boolean = false;
    private var _mode:Number = 0;
    private var _tooltip:Tooltip;
    private var _places:Array = new Array();
    
    
    public function get readOnly():Boolean {
      return _read_only;
    }
    public function set readOnly(r:Boolean):void {
      _read_only = r;
    }
    
    /**
     * Constructor
     * 
     */
    public function MapContainer() {
    
      var self: Object = this;
      setMode(MODE_DRAG_MAP);
      
      this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
      this.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
      this.addEventListener(MouseEvent.MOUSE_OUT, onMouseUp);
      this.addEventListener(MouseEvent.CLICK, onMouseClick);
      this.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseScroll);
      
      _tooltip = new Tooltip();
      root.stage.addChild(_tooltip);
      
      // Set slider properties and event handlers
      slider.minimum = MIN_SCALE;
      slider.maximum = MAX_SCALE;
      slider.snapInterval = slider.tickInterval = 1;
      slider.addEventListener(SliderEvent.CHANGE, onSliderChange);
      slider.addEventListener(SliderEvent.THUMB_PRESS, onSliderPress);
      
      plus.addEventListener(MouseEvent.CLICK, function(e){
        slider.value++;
        scaleTo(slider.value);
      });
      minus.addEventListener(MouseEvent.CLICK, function(e){
        slider.value--;
        scaleTo(slider.value);
      });
    }
    
    /**
     * Shows tooltip balloon with name of the place
     * 
     */
    public function showPlaceTitle(key) {
      var place:Object = getPlace(key);
      if (!place) return false;
      var button = place.button;
      var newPoint:Point = _getPlacePoint(place);
      
      if (newPoint.x < 20 || newPoint.x > mapMask.width - 130 || newPoint.y < 50 || newPoint.y > mapMask.height - 20) {
        shiftTo(mapMask.width / 2 - button.x * map.scaleX, mapMask.height / 2 - button.y * map.scaleY, function() {
          var newPoint:Point = _getPlacePoint(place);
          _tooltip.show(newPoint.x,newPoint.y, place.title);
        });
      } else { 
        _tooltip.show(newPoint.x,newPoint.y, place.title);
      }
    }
    
    
    /**
     * Puts place circle on the map and push the element to _places array
     * 
     */
    public function putPlace(key:Number, x:Number, y:Number, title:String = ""):Object {
      
      var button:PlaceButton = new PlaceButton();
      button.x = x;
      button.y = y;
      button.scaleX = 1 /  (mapMask.width / map.width * map.scaleX * _scale);
      button.scaleY = 1 /  (mapMask.width / map.width * map.scaleX * _scale);
      button.name = key.toString(); // button var key
      button.addEventListener(MouseEvent.MOUSE_OVER, function(e:MouseEvent) {
        var place:Object = getPlace(key);
        _tooltip.show(e.stageX, e.stageY, place.title);
        map.removeChild(button);
        map.addChild(button);
      });
      button.addEventListener(MouseEvent.MOUSE_OUT, function(e:MouseEvent) {
        _tooltip.hide(1000);
      });
      button.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) {
        if (_mode == MODE_MODAL_WIN) {
          return;
        }
        if (_read_only) return;
        var event:MapEvent = new MapEvent(CLICK_ON_PLACE);
        event.placeKey = key;
        dispatchEvent(event);
      });
      map.addChild(button);
      _places.push({x: x, y:y, title: title, button: button, key:key});
      return getLastPlace();
    }
    
    /**
     * Returns place object by key
     * 
     */
    public function getPlace(key):Object {
      for (var i:String in _places) {
        if (_places[i].key == key) {
          return Utils.clone(_places[i]);
        }
      }
      return false;
    }
    
    /**
     * Returns last added place object
     * 
     */
    public function getLastPlace():Object {
      return Utils.clone(_places[_places.length - 1]);
    }
    
    /**
     * Returns a copy of all places array
     * 
     */
    public function getAllPlaces() {
      return Utils.clone(_places);
    }
    
    /**
     * Sets title of the place by key
     * 
     */
    public function setPlaceTitle(key, title:String) {
      var place:Object;
      for (var i:String in _places) {
        if (_places[i].key == key) {
          place = _places[i];
          break;
        }
      }
      if (place != null)
        place.title = title;
      else 
        return false;
    }
    
    /**
     * Remove the place by key
     * 
     */
    public function removePlace(key) {
      for (var i:String in _places) {
        if (_places[i].key == key) {
          map.removeChild(_places[i].button);
          _places.splice(i, 1);
          return;
        }
      }
    }
    
    /**
     * Resets size and scale of the map
     * 
     */
    public function resetSize() {
      scaleTo(1);
      shiftTo(0, 20);
    }
    
    /**
     * Scales the map
     * 
     */
    public function scaleTo(scale:Number, callback: Function = null) {
      _tooltip.hide();
      if (scale > MAX_SCALE) scale = MAX_SCALE;
      if (scale < MIN_SCALE) scale = MIN_SCALE;
      scale = Math.pow(1.7, scale - 1);
      _scale = scale;
      scale = mapMask.width / map.width * map.scaleX * scale;
      var newWidth = map.width / map.scaleX * scale;
      var newHeight = map.height / map.scaleY * scale;
      var newX = mapMask.width / 2 - (mapMask.width / 2 - map.x) / map.width * newWidth;
      var newY = mapMask.height / 2 - (mapMask.height / 2 - map.y) / map.height * newHeight;
      
      Tweener.addTween(map, {
        time: 0.5
        ,scaleX: scale
        ,scaleY: scale
        ,x:newX
        ,y:newY
        ,transition:"easeInOutCubic"
        ,onComplete: function(){
          for(var i:String in _places) {
            _places[i].button.scaleX = 1/scale;
            _places[i].button.scaleY = 1/scale;
          }
          if (callback != null) callback();
        }
      });
    }
    
    
    /**
     * Shifts the map
     * 
     */
    public function shiftTo(x:Number, y:Number, callback: Function = null) {
      _tooltip.hide();
      if (x + map.width < 30) x = - map.width + 30;
      if (x > mapMask.width - 30) x = mapMask.width;
      if (y + map.height < 30) y = - map.height + 30;
      if (y > mapMask.height - 30) y = mapMask.height;
      
      Tweener.addTween(map, {time: 0.5, x: x, y: y, transition:"easeInOutCubic", onComplete: callback});
    }
    
    /**
     * Sets mapContainer behavior mode
     * 
     */
    public function setMode(mode:Number) {
      _mode = mode;
      if (mode == MODE_PUT_PLACE || mode == MODE_DRAG_MAP) {
        this.buttonMode = true;
      } else {
        this.buttonMode = false;
      }
    }
    
    
    /**
     * Event handler on slider value change
     * 
     */
    public function onSliderChange(e:SliderEvent) {
      scaleTo(e.value);
      setMode(MODE_DRAG_MAP);
    }
    
    /**
     * Event handler on slider mouse press
     * 
     */
    public function onSliderPress(e:SliderEvent) {
      setMode(MODE_SCALE);
    }
    
    /**
     * Event hadler on mouse down
     * 
     */
    public function onMouseDown(e:MouseEvent) {
      _tooltip.hide();
      if (_mode == MODE_DRAG_MAP) {
        map.startDrag();
      }
    }
    
    /**
     * Event hadler on mouse up
     * 
     */
    public function onMouseUp(e:MouseEvent) {
      if (_mode == MODE_DRAG_MAP) {
        map.stopDrag();
      }
    }
    
    /**
     * Event handler on mouse click
     * 
     */
    public function onMouseClick(e:MouseEvent) {
      if (_mode != MODE_PUT_PLACE) {
        e.stopImmediatePropagation();
      }
    }
    
    /**
     * Event handler on mouse scroll (wheel)
     * 
     */
    public function onMouseScroll(e:MouseEvent) {
      slider.value += (e.delta > 0 ? 1 : -1);
      scaleTo(slider.value);
    }
    
    /**
     * Gets global coordinates of the place
     * 
     */
    private function _getPlacePoint(place:Object):Point {
      var button = place.button;
      var newPoint:Point = new Point(map.x + button.x * map.scaleX, map.y +  button.y * map.scaleY);
      return this.localToGlobal(newPoint);
    }
  }
}